package it.antonio.ilpizze.ioc;

import java.util.HashMap;
import java.util.Map;
import java.util.Stack;

public class Dependencies {
	
	private Map<Object, Builder<?>> builders = new HashMap<Object, Builder<?>>();
	private Map<Class<?>, InstanceDecorator> decorators = new HashMap<Class<?>, InstanceDecorator>();
	
	private final Stack<Builder<?>> dependencyStack = new Stack<Builder<?>>();
	
	public Dependencies() {
		super();
	}

	
	public void define(Builder<?> builder) {
		builders.put(builder.getKey(), builder);
	}
	public void define(InstanceDecorator decorator) {
		decorators.put(decorator.getType(), decorator);
	}
	
	public <T> T get(Object key) {
		// start construction
		
		// Getting the builder.
		@SuppressWarnings("unchecked")
		Builder<T> builder = (Builder<T>) builders.get(key);
		
		
		// If we didn't find any builder, let's ask the parent container.
		if (builder == null) {

			String callingClass = dependencyStack.isEmpty() ? "" : " Calling class: " + dependencyStack.peek().getType();
			throw new IocException(key + " is not defined in the container." + callingClass);
		}

		if (dependencyStack.contains(key)) {
			throw new IocException("Cyclic dependency on builder [" + key + "]");
		}

		T instance;

		dependencyStack.push(builder);
		try {
			instance = (T) builder.build(this);
		} catch (IocSignalException e) {
			throw e;
		} catch (IocException e) {
			throw e;
		} catch (Exception e) {
			throw new IocSignalException(e);
		}
		// Cleaning the stack.
		dependencyStack.pop();
		return instance;
	}
	
	public Class<?> getCallingClass() {
		Builder<?> pop = dependencyStack.pop();
		Class<?> callingClass = dependencyStack.peek().getType();
		dependencyStack.push(pop);
		return callingClass;
	}


	public void decorate(Object instance) {
		Class<?> type = instance.getClass();
		InstanceDecorator instanceDecorator = decorators.get(type);
		if(instanceDecorator != null){
			instanceDecorator.decorate(instance, this);
		} else {
			throw new IocException("Instance Decorator not Defined for " + type.getSimpleName());
		}
		
	}
	
}
